#define _CRT_SECURE_NO_WARNINGS

#include "pe.h"
#include "file_cmd.h"
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>

/****************************************************************************\
 * Private Variables
\****************************************************************************/

static wchar_t  s_cur_file_path    [FILENAME_MAX + 1] = { L'\0' };
static wchar_t  s_cur_file_dir     [FILENAME_MAX + 1] = { L'\0' };
static wchar_t  s_cur_file_title   [FILENAME_MAX + 1] = { L'\0' };
static byte    *s_cur_file_content;
static size_t   s_cur_file_len;
static BOOL     s_file_ready       = 0;

static str_wc s_empty_file_title = L"";

/****************************************************************************\
 * Private Functions
\****************************************************************************/

/*
 * Sets static variables: s_cur_file_path, s_cur_file_dir, and
 * s_cur_file_title with the given file path.
 *
 * Parameters:
 *   path - Full or relative path of the file.
 *
 * Returns: TRUE for success, FALSE for error.
 */
static BOOL s_set_file_path(str_wc path)
{
	int path_len, del_pos;

	// Get file path.
	path_len = wcslen(path);

	if(path_len > FILENAME_MAX)
	{
		wprintf(L"Path too long (%d chars), max %d.\n", path_len, FILENAME_MAX);
		return FALSE;
	}
	wcsncpy(s_cur_file_path, path, path_len);
	s_cur_file_path[path_len] = L'\0';

	// Get file dir and title.
	del_pos = path_len - 1;
	while(del_pos >= 0
		&& s_cur_file_path[del_pos] != L'\\'
		&& s_cur_file_path[del_pos] != L'/')
		--del_pos;

	wcsncpy(s_cur_file_dir, s_cur_file_path, del_pos + 1);
	s_cur_file_dir[del_pos + 1] = L'\0';

	wcsncpy(s_cur_file_title, s_cur_file_path + del_pos + 1, path_len - del_pos - 1);
	s_cur_file_title[path_len - del_pos - 1] = L'\0';

	return TRUE;
}

/*
 * Get length (in bytes) of the file.
 *
 * Parameters:
 *     pf - Pointer to the give FILE struct.
 *   size - (Out) pointer to the size of the file.
 *
 * Returns: TRUE for success, FALSE for error.
 */
static BOOL s_get_file_len(FILE *pf, size_t *size)
{
	fpos_t file_end;

	if(fseek(pf, 0, SEEK_END) != 0)
	{
		wprintf(L"Cannot seek the file end.\n");
		return FALSE;
	}

	if(fgetpos(pf, &file_end) != 0)
	{
		wprintf(L"Cannot get the file end.\n");
		return FALSE;
	}

	*size = (size_t)file_end;
	return TRUE;
}

/*
 * Read contents of the file (in binary) into the buffer.
 *
 * Parameters:
 *        pf - Pointer to the give FILE struct.
 *   content - (Out) pointer to the buffer that saves the file contents.
 *
 * Returns: TRUE for success, FALSE for error.
 */
static BOOL s_read_file(FILE *pf, byte *content)
{
	size_t size, n_read;

	if(!s_get_file_len(pf, &size))
		return FALSE;

	if(fseek(pf, 0, SEEK_SET) != 0)
	{
		wprintf(L"Cannot seek the file beginning.\n");
		return FALSE;
	}

	errno = 0;
	n_read = fread(content, 1, size, pf);

	if(errno)
	{
		wprintf(L"Error to read file.\n");
		return FALSE;
	}

	if(ferror(pf))
	{
		wprintf(L"Error in the file stream when reading.\n");
		return FALSE;
	}

	if(n_read < size)
	{
		wprintf(L"Read file uncompletely.\n");
		return FALSE;
	}

	return TRUE;
}

/*
 * Open file at the given path in binray read mode.
 *
 * Parameters:
 *   path - Path of the file to open.
 *
 * Returns: The opened file, NULL for error.
 */
static FILE *s_open_file(str_wc path)
{
	FILE *pf;

	wprintf(L"Opening... %s\n", path);

	errno = 0;
	pf = _wfopen(path, L"rb");

	if(pf == NULL)
	{
		switch(errno)
		{
		case ENOENT: wprintf(L"File not found.\n");                     break;
		default:     wprintf(L"Fail to open file, errno=%d.\n", errno); break;
		}
	}

	return pf;
}

/*
 * Print address in hexadecimal format.
 *
 * Parameters:
 *   address - Pointer to the bytes to print.
 *
 * Returns: None.
 */
static void s_print_address(size_t address)
{
	wprintf(L"%08Xh", address);
}

static void s_print_offset()
{
	int i;

	//        01234567h:  1  2...
	wprintf(L"          ");
	for(i = 0; i < 16; ++i)
		wprintf(L" +%X", i);
	wprintf(L"\n");
}

static void s_print_line_hex(byte *line, size_t skip, size_t len)
{
	size_t i;
	size_t remain = 16 - skip - len;

	for(i = 0; i < skip; ++i)
		wprintf(L"   ");

	for(i = 0; i < len; ++i)
		wprintf(L" %02X", line[i]);

	for(i = 0; i < remain; ++i)
		wprintf(L"   ");
}

static void s_print_line_ascii(byte *line, size_t skip, size_t len)
{
	size_t i;
	byte b;

	for(i = 0; i < skip; ++i)
		wprintf(L" ");

	for(i = 0; i < len; ++i)
	{
		b = line[i];
		wprintf(L"%c", iscntrl(b) ? '.' : b);
	}
}

/*
 * Print bytes in hexadecimal format.
 *
 * Parameters:
 *   bytes - Pointer to the bytes to print.
 *     len - Number of bytes to print.
 *
 * Returns: None.
 */
static void s_print_bytes(byte *bytes, size_t start, size_t len)
{
	size_t n_skip, n_remain, n_line, y;
	byte *line_start;

	n_skip = start % 16;
	n_remain = (len + n_skip) % 16;
	if(n_remain == 0) n_remain = 16;
	n_line = (len + n_skip + 15) / 16;

	line_start = bytes + start - n_skip;

	if(n_line == 1)
	{
		s_print_offset();
		s_print_address(line_start - bytes);
		wprintf(L":");
		s_print_line_hex(line_start + n_skip, n_skip, n_remain - n_skip);
		wprintf(L"        ");
		s_print_line_ascii(line_start + n_skip, n_skip, n_remain - n_skip);
		wprintf(L"\n");
		return;
	}

	// First line:
	s_print_offset();
	s_print_address(line_start - bytes);
	wprintf(L":");
	s_print_line_hex(line_start + n_skip, n_skip, 16 - n_skip);
	wprintf(L"        ");
	s_print_line_ascii(line_start + n_skip, n_skip, 16 - n_skip);
	wprintf(L"\n");

	// Middle lines:
	for(y = 1; y < n_line - 1; ++y)
	{
		if(y % 16 == 0)
			s_print_offset();

		line_start = bytes + start + y * 16 - n_skip;
		s_print_address(line_start - bytes);
		wprintf(L":");
		s_print_line_hex(line_start, 0, 16);
		wprintf(L"        ");
		s_print_line_ascii(line_start, 0, 16);
		wprintf(L"\n");
	}

	// Last line:
	if(n_remain > 0)
	{
		line_start = bytes + start + (n_line - 1) * 16 - n_skip;
		s_print_address(line_start - bytes);
		wprintf(L":");
		s_print_line_hex(line_start, 0, n_remain);
		wprintf(L"        ");
		s_print_line_ascii(line_start, 0, n_remain);
		wprintf(L"\n");
	}
}

/****************************************************************************\
 * Public Functions
\****************************************************************************/

BOOL check_file_opened(void)
{
	if(!s_file_ready)
		wprintf(L"File not ready.\n");

	return s_file_ready;
}

str_wc get_cur_file_title(void)
{
	return s_file_ready ? s_cur_file_title : s_empty_file_title;
}

void exec_open(str_wc path, pe_file *p_pe)
{
	FILE *pf;

	s_file_ready = FALSE;

	if(!s_set_file_path(path)) return;
	if(!(pf = s_open_file(path))) return;

	if(!s_get_file_len(pf, &s_cur_file_len)) goto close_and_exit;
	
	wprintf(L"Length: %d bytes.\n", s_cur_file_len);

	s_cur_file_content = (byte *)realloc(s_cur_file_content, s_cur_file_len);
	if(s_cur_file_content == NULL)
	{
		wprintf(L"Cannot read file, no enough memory.\n");
		goto close_and_exit;
	}

	if(!s_read_file(pf, s_cur_file_content)) goto close_and_exit;

	set_pe_file_content(p_pe, s_cur_file_content, s_cur_file_len);

	s_file_ready = TRUE;

close_and_exit:
	fclose(pf);
}

void exec_refresh(pe_file *p_pe)
{
	if(s_file_ready)
		exec_open(s_cur_file_path, p_pe);
	else
		wprintf(L"No curent file is opened.\n");
}

void exec_file(void)
{
	if(s_file_ready)
		wprintf(L"%s\nLength: %d bytes.\n", s_cur_file_path, s_cur_file_len);
	else
		wprintf(L"No curent file is opened.\n");
}

void exec_dump_data(size_t start, size_t len)
{
	if(!check_file_opened())
		return;

	if(start >= s_cur_file_len)
	{
		wprintf(L"Starting of range beyond end of the file.\n");
		return;
	}

	if(start + len > s_cur_file_len)
	{
		wprintf(L"Ending of range beyond end of the file.\n");
		return;
	}

	wprintf(L"Dumping ");
	s_print_address(start);
	wprintf(L" - ");
	s_print_address(start + len - 1);
	wprintf(L", ");
	s_print_address(len);
	wprintf(L" (%d) bytes in total.\n", len);
	s_print_bytes(s_cur_file_content, start, len);
}
